﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.IO;
using System.Diagnostics;
using GalaSoft.MvvmLight.Messaging;
using JmShared;
using WebSocket4Net;
using JiveMessenger.Utilities;
using JmShared.Models;
using NLog;

namespace JiveMessenger.Network
{
    public class NetworkConnection
    {
        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
        private bool _isTempConnect = false;
        private string _clientVersion;
        private int _messageID;
        private string _deviceID;
        private JiveJsonWebSocket _webSocketConnection;
        private string _currentUserID;
        private string _currentDeviceID;
        private int _sendtMessageCount;
        private DateTime _lastSentMessageTime;

        public event EventHandler<EventArgs> OnDisconnected;
        public event EventHandler<ChatMessageEventArgs> ChatMessageReceived;
        public event EventHandler<ChatMessageEventArgs> ChatMessageSendt;
        public event EventHandler<ChatReceiptEventArgs> ChatReceiptReceived;
        public event EventHandler<ContactEmoticonEventArgs> ContactEmoticonReceived;
        public event EventHandler<AvatarEventArgs> AvatarReceived;
        public event EventHandler<ClientListEventArgs> ClientListReceived;
        public event EventHandler<UserSettingsEventArgs> UserSettingsReceived;
        public event EventHandler<ServerInfoMessageEventArgs> ServerInfoMessageReceived;
        public event EventHandler<DeviceStatusEventArgs> DeviceStatusReceived;
        public event EventHandler<NotificationListEventArgs> NotificationListReceived;
        public event EventHandler<LoginResponseEventArgs> LoginResponseReceived;
        public event EventHandler<EmoticonRequestEventArgs> EmoticonRequestReceived;
        public event EventHandler<AvatarRequestEventArgs> AvatarRequestReceived;
        public event EventHandler<MessageQueueEventArgs> MessageQueueReceived;
        public event EventHandler<FileSliceEventArgs> FileSliceReceived;

        public NetworkConnection(string uniqueDeviceID, string clientVersion)
        {
            this._deviceID = uniqueDeviceID;
            this._clientVersion = clientVersion;
        }

        public T SendReceiveObject<T>(string packetheader, object obj, int timeout = 1000)
        {
            AutoResetEvent sendReceiveEvent = new AutoResetEvent(false);
            var returnVal = default(T);
            this._webSocketConnection.Query<T>(packetheader, obj, (o) =>
            {
                sendReceiveEvent.Set();
                returnVal = o;
            });
            sendReceiveEvent.WaitOne(timeout);

            return returnVal;
        }

        public DatabaseQueryResponse VerifyPasswordResetEmail(VerifyMail msg, string server, int port)
        {
            try
            {
                DatabaseQueryResponse result = null;
                bool connectSuccess = this.Connect(server, port, true);
                if (connectSuccess)
                {
                    result = SendReceiveObject<DatabaseQueryResponse>(ClientCommands.VerifyResetPasswordMail.ToString(), msg, 6000);
                    if (result == null)
                        result = new DatabaseQueryResponse(false, "Timeout contacting the server");
                    _webSocketConnection.Close();
                }
                else
                {
                    result = new DatabaseQueryResponse(false, "An error occured contacting the server");
                }
                return result;
            }
            finally
            {
                if (_webSocketConnection != null)
                    _webSocketConnection.Close();
            }
        }

        public DatabaseQueryResponse ResetPassword(PasswordReset pr, string server, int port)
        {
            try
            {
                DatabaseQueryResponse result = null;
                bool connectSuccess = this.Connect(server, port, true);
                if (connectSuccess)
                {
                    result = SendReceiveObject<DatabaseQueryResponse>(ClientCommands.ResetPassword.ToString(), pr, 6000);
                    if (result == null)
                        result = new DatabaseQueryResponse(false, "Timeout contacting the server");
                }
                else
                {
                    result = new DatabaseQueryResponse(false, "An error occured contacting the server");
                }
                return result;
            }
            finally
            {
                if (_webSocketConnection != null)
                    _webSocketConnection.Close();
            }
        }

        public DatabaseQueryResponse VerifyEmail(VerifyMail msg, string server, int port)
        {
            try
            {
                DatabaseQueryResponse result = null;
                bool connectSuccess = this.Connect(server, port, true);
                if (connectSuccess)
                {
                    result = SendReceiveObject<DatabaseQueryResponse>(ClientCommands.VerifyRegisterAccountMail.ToString(), msg, 6000);
                    if (result == null)
                        result = new DatabaseQueryResponse(false, "Timeout contacting the server");
                    _webSocketConnection.Close();
                }
                else
                {
                    result = new DatabaseQueryResponse(false, "An error occured contacting the server");
                }
                return result;
            }
            finally
            {
                if (_webSocketConnection != null)
                    _webSocketConnection.Close();
            }
        }

        public DatabaseQueryResponse RegisterUser(RegisterUser reg, string server, int port)
        {
            try
            {
                DatabaseQueryResponse result = null;
                bool connectSuccess = this.Connect(server, port, true);
                if (connectSuccess)
                {
                    result = SendReceiveObject<DatabaseQueryResponse>(ClientCommands.RegisterAccount.ToString(), reg, 6000);
                    if (result == null)
                        result = new DatabaseQueryResponse(false, "Timeout contacting the server");
                    _webSocketConnection.Close();
                }
                else
                {
                    result = new DatabaseQueryResponse(false, "An error occured contacting the server");
                }
                return result;
            }
            finally
            {
                if (_webSocketConnection != null)
                    _webSocketConnection.Close();
            }
        }

        public DatabaseQueryResponse SendFriendRequest(ChatMessage msg)
        {
            var result = SendReceiveObject<DatabaseQueryResponse>(ClientCommands.Message.ToString(), msg, 6000);
            if (result == null)
                result = new DatabaseQueryResponse(false, "Timeout contacting the server");
            return result;
        }

        public DatabaseQueryResponse SendCreateGroupRequest(ChatMessage msg)
        {
            var result = SendReceiveObject<DatabaseQueryResponse>(ClientCommands.Message.ToString(), msg, 6000);
            if (result == null)
                result = new DatabaseQueryResponse(false, "Timeout contacting the server");
            return result;
        }
        public DatabaseQueryResponse SendRenameGroupRequest(ChatMessage msg)
        {
            var result = SendReceiveObject<DatabaseQueryResponse>(ClientCommands.Message.ToString(), msg, 6000);
            if (result == null)
                result = new DatabaseQueryResponse(false, "Timeout contacting the server");
            return result;
        }
        public DatabaseQueryResponse SendGroupInvite(ChatMessage msg)
        {
            var result = SendReceiveObject<DatabaseQueryResponse>(ClientCommands.Message.ToString(), msg, 6000);
            if (result == null)
                result = new DatabaseQueryResponse(false, "Timeout contacting the server");
            return result;
        }

        public DatabaseQueryResponse RemoveMemberFromGroup(ChatMessage msg)
        {
            var result = SendReceiveObject<DatabaseQueryResponse>(ClientCommands.Message.ToString(), msg, 6000);
            if (result == null)
                result = new DatabaseQueryResponse(false, "Timeout contacting the server");
            return result;
        }

        public DatabaseQueryResponse SetGroupPrivilegeForUser(MemberGroupPrivilege priv)
        {
            var result = SendReceiveObject<DatabaseQueryResponse>("SetGroupPrivilege", priv, 6000);
            if (result == null)
                result = new DatabaseQueryResponse(false, "Timeout contacting the server");
            return result;
        }

        /// <summary>
        /// Sends the chatmessage to the server. If messages are sent too fast, an OperationCanceledException will be thrown.
        /// </summary>
        /// <param name="msg"></param>
        /// <exception cref="OperationCanceledException"></exception>
        public bool SendMessage(ChatMessage msg)
        {
            if (this._webSocketConnection == null)
                return false;
            if (this._webSocketConnection.State != WebSocketState.Open)
                return false;
            try
            {
                if (msg.MessageType == MessageType.GroupMessage || msg.MessageType == MessageType.PrivateMessage)
                {
                    if (DateTime.UtcNow.Subtract(_lastSentMessageTime).TotalMilliseconds < 500)
                        _sendtMessageCount++;
                    else
                        _sendtMessageCount = 0;

                    _lastSentMessageTime = DateTime.UtcNow;

                    if (_sendtMessageCount > 7)
                    {
                        throw new OperationCanceledException("You can't send messages this often");
                    }
                    msg.MessageID = Interlocked.Increment(ref _messageID);
                }

                this._webSocketConnection.Send(ClientCommands.Message.ToString(), msg);

                if (ChatMessageSendt != null)
                    ChatMessageSendt(this, new ChatMessageEventArgs(msg));
            }
            catch (OperationCanceledException)
            {
                throw;
            }
            catch (Exception err)
            {
                Logger.ErrorException("Error sending message", err);
                return false;
            }
            return true;
        }


        public bool SendCommand(object content, ClientCommands command)
        {
            if (this._webSocketConnection == null)
                return false;
            if (this._webSocketConnection.State != WebSocketState.Open)
                return false;
            try
            {
                if (command == ClientCommands.Message)
                    return SendMessage(content as ChatMessage);
                this._webSocketConnection.Send(command.ToString(), content);
            }
            catch (Exception err)
            {
                Logger.ErrorException(string.Format("Error sending {0}", command.ToString()), err);
                return false;
            }
            return true;
        }

        /// <summary>
        /// Forces a disconnect without sending log out message.
        /// </summary>
        public void Disconnect()
        {
            this._webSocketConnection.Close();
        }
        /// <summary>
        /// Log out from the server
        /// </summary>
        /// <returns></returns>
        public void Logout()
        {
            SendMessage(new ChatMessage(this._currentDeviceID, this._currentUserID, MessageType.Logout));
            Disconnect();
        }

        public bool Login(string server, string username, string password, int port)
        {
            try
            {
                if (Connect(server, port))
                {
                    UserInfo user = new UserInfo(username, _deviceID)
                    {
                        ClientStatus = ClientStatus.Available,
                    };
                    _currentUserID = user.UserID;
                    _currentDeviceID = user.ID;

                    LoginRequest info = new LoginRequest(username, StaticMethods.EncodePassword(StaticMethods.ToInsecureString(StaticMethods.DecryptString(password))), _clientVersion, user);
                    SendCommand(info, ClientCommands.Login);


                    return true;
                }
                return false;
            }
            catch (Exception err)
            {
                Logger.ErrorException("Error in Login", err);
                return false;
            }
        }

        /// <summary>
        /// Connects to the server
        /// </summary>
        /// <returns></returns>
        private bool Connect(string server, int port, bool tempConnect = false)
        {
            try
            {
                this._isTempConnect = tempConnect;
                if (_webSocketConnection != null)
                {
                    if (_webSocketConnection.State == WebSocketState.Open)
                        _webSocketConnection.Close();

                    _webSocketConnection = null;
                }

                if (String.IsNullOrEmpty(server))
                {
                    Messenger.Default.Send("No servers configured");
                    if (!tempConnect)
                    {
                        if (OnDisconnected != null)
                            OnDisconnected(this, null);
                    }
                    return false;
                }
                _webSocketConnection = new JiveJsonWebSocket(String.Format("wss://{0}:{1}", server, port));
                _webSocketConnection.AllowUnstrustedCertificate = true;
                _webSocketConnection.EnableAutoSendPing = true;
                _webSocketConnection.AutoSendPingInterval = 5;
                _webSocketConnection.On<ServerInfoMessage>(ServerCommands.ServerInfoMessage.ToString(), ProcessServerInfoMessage);
                _webSocketConnection.On<ChatMessage>(ServerCommands.Message.ToString(), ProcessMessage);
                _webSocketConnection.On<ChatReceipt>(ServerCommands.ChatReceipt.ToString(), ProcessReceipt);
                _webSocketConnection.On<ClientList>(ServerCommands.ClientList.ToString(), ProcessClientList);
                _webSocketConnection.On<DeviceStatus>(ServerCommands.DeviceStatusUpdate.ToString(), ProcessDeviceStatus);
                _webSocketConnection.On<ContactEmoticonTransfer>(ServerCommands.CustomEmoticon.ToString(), ProcessEmoticon);
                _webSocketConnection.On<NotificationList>(ServerCommands.NotificationsUpdate.ToString(), ProcessNotifications);
                _webSocketConnection.On<PcLoginResponse>(ServerCommands.LoginResponse.ToString(), ProcessLoginResponse);
                _webSocketConnection.On<AvatarTransfer>(ServerCommands.Avatar.ToString(), ProcessAvatar);
                _webSocketConnection.On<AvatarRequest>(ServerCommands.RequestAvatar.ToString(), ProcessAvatarRequest);
                _webSocketConnection.On<EmoticonRequest>(ServerCommands.RequestEmoticon.ToString(), ProcessEmoticonRequest);
                _webSocketConnection.On<MessageQueue>(ServerCommands.MessageQueue.ToString(), ProcessMessageQueue);
                _webSocketConnection.On<FileSlice>(ServerCommands.FileTransfer.ToString(), ProcessFileTransfer);
              
                _webSocketConnection.Opened += new EventHandler(websocket_Opened);
                _webSocketConnection.Error += new EventHandler<SuperSocket.ClientEngine.ErrorEventArgs>(websocket_Error);
                _webSocketConnection.Closed += new EventHandler(websocket_Closed);


                _webSocketConnection.Open();
                int n = 0;
                int timeoutInSeconds = 10;
                while (_webSocketConnection.State == WebSocketState.Connecting)
                {
                    Thread.Sleep(1);
                    n++;
                    if (n > (timeoutInSeconds * 1000))
                    {
                        n = 0;
                        break;
                    }
                }
                if (_webSocketConnection.State != WebSocketState.Open)
                {
                    _webSocketConnection.Close();
                    return false;
                }

                return true;
            }
            catch
            {
                throw;

            }
        }



        private void websocket_Closed(object sender, EventArgs e)
        {
            if (_webSocketConnection != null)
            {
                _webSocketConnection.Opened -= new EventHandler(websocket_Opened);
                _webSocketConnection.Error -= new EventHandler<SuperSocket.ClientEngine.ErrorEventArgs>(websocket_Error);
            }
            _currentUserID = string.Empty;
            if (OnDisconnected != null && !_isTempConnect)
                OnDisconnected(this, null);
            _isTempConnect = false;
        }

        private void websocket_Error(object sender, SuperSocket.ClientEngine.ErrorEventArgs e)
        {
            Logger.ErrorException("Error in websocket connection", e.Exception);
        }

        private void websocket_Opened(object sender, EventArgs e)
        {

        }

        private void ProcessMessageQueue(JsonWebSocket conn, MessageQueue queue)
        {
            if (MessageQueueReceived != null)
                MessageQueueReceived(this, new MessageQueueEventArgs(queue));
            Logger.Debug("Received message of type MessageQueue");
        }
        private void ProcessFileTransfer(JsonWebSocket conn, FileSlice fileSlice)
        {
            if (FileSliceReceived != null)
                FileSliceReceived(this, new FileSliceEventArgs(fileSlice));
            Logger.Debug("Received message of type FileSlice");
        }
        private void ProcessEmoticonRequest(JsonWebSocket conn, EmoticonRequest request)
        {
            if (EmoticonRequestReceived != null)
                EmoticonRequestReceived(this, new EmoticonRequestEventArgs(request));
            Logger.Debug("Received message of type EmoticonRequest");
        }

        private void ProcessAvatarRequest(JsonWebSocket conn, AvatarRequest request)
        {
            if (AvatarRequestReceived != null)
                AvatarRequestReceived(this, new AvatarRequestEventArgs(request));
            Logger.Debug("Received message of type AvatarRequest");
        }
        private void ProcessLoginResponse(JsonWebSocket conn, LoginResponse response)
        {
            if (LoginResponseReceived != null)
                LoginResponseReceived(this, new LoginResponseEventArgs(response));
            Logger.Debug("Received message of type LoginResponse");
        }
        private void ProcessNotifications(JsonWebSocket conn, NotificationList notificationList)
        {
            if (NotificationListReceived != null)
                NotificationListReceived(this, new NotificationListEventArgs(notificationList));
            Logger.Debug("Received message of type NotificationList");
        }
        private void ProcessDeviceStatus(JsonWebSocket conn, DeviceStatus deviceStatus)
        {
            if (DeviceStatusReceived != null)
                DeviceStatusReceived(this, new DeviceStatusEventArgs(deviceStatus));
            Logger.Debug("Received message of type DeviceStatus");
        }
        private void ProcessServerInfoMessage(JsonWebSocket conn, ServerInfoMessage serverInfoMsg)
        {
            if (ServerInfoMessageReceived != null)
                ServerInfoMessageReceived(this, new ServerInfoMessageEventArgs(serverInfoMsg));
            Logger.Debug("Received message of type ServerInfoMessage");
        }

        private void ProcessUserSettings(JsonWebSocket conn, PcUserSettings settings)
        {
            if (UserSettingsReceived != null)
                UserSettingsReceived(this, new UserSettingsEventArgs(settings));
            Logger.Debug("Received message of type PcUserSettings");
        }

        private void ProcessClientList(JsonWebSocket conn, ClientList clients)
        {
            if (ClientListReceived != null)
                ClientListReceived(this, new ClientListEventArgs(clients));
            Logger.Debug("Received message of type ClientList");
        }

        private void ProcessEmoticon(JsonWebSocket conn, ContactEmoticonTransfer emoticon)
        {
            if (ContactEmoticonReceived != null)
                ContactEmoticonReceived(this, new ContactEmoticonEventArgs(emoticon));
            Logger.Debug("Received message of type ContactEmoticon");
        }
        private void ProcessAvatar(JsonWebSocket conn, AvatarTransfer avatar)
        {
            if (AvatarReceived != null)
                AvatarReceived(this, new AvatarEventArgs(avatar));
            Logger.Debug("Received message of type Avatar");
        }

        private void ProcessReceipt(JsonWebSocket conn, ChatReceipt receipt)
        {
            if (ChatReceiptReceived != null)
                ChatReceiptReceived(this, new ChatReceiptEventArgs(receipt));
            Logger.Debug("Received message of type ChatReceipt");
        }

        private void ProcessMessage(JsonWebSocket conn, ChatMessage msg)
        {
            if (ChatMessageReceived != null)
                ChatMessageReceived(this, new ChatMessageEventArgs(msg));
            Logger.Debug("Received message of type ChatMessage");
        }
    }
    public class FileSliceEventArgs : EventArgs
    {
        public FileSlice FileSlice { get; private set; }
        public FileSliceEventArgs(FileSlice slice)
        {
            FileSlice = slice;
        }
    }
    public class MessageQueueEventArgs : EventArgs
    {
        public MessageQueue MessageQueue { get; private set; }
        public MessageQueueEventArgs(MessageQueue queue)
        {
            MessageQueue = queue;
        }
    }
    public class AvatarRequestEventArgs : EventArgs
    {
        public AvatarRequest AvatarRequest { get; set; }
        public AvatarRequestEventArgs(AvatarRequest request)
        {
            this.AvatarRequest = request;
        }
    }

    public class EmoticonRequestEventArgs : EventArgs
    {
        public EmoticonRequest EmoticonRequest { get; set; }
        public EmoticonRequestEventArgs(EmoticonRequest request)
        {
            this.EmoticonRequest = request;
        }
    }
    public class LoginResponseEventArgs : EventArgs
    {
        public LoginResponse LoginResponse { get; set; }
        public LoginResponseEventArgs(LoginResponse response)
        {
            this.LoginResponse = response;
        }
    }

    public class NotificationListEventArgs : EventArgs
    {
        public NotificationList NotificationList { get; set; }
        public NotificationListEventArgs(NotificationList list)
        {
            this.NotificationList = list;
        }
    }
    public class ChatMessageEventArgs : EventArgs
    {
        public ChatMessage Message { get; set; }
        public ChatMessageEventArgs(ChatMessage msg)
        {
            this.Message = msg;
        }
    }
    public class DeviceStatusEventArgs : EventArgs
    {
        public DeviceStatus Status { get; set; }
        public DeviceStatusEventArgs(DeviceStatus status)
        {
            this.Status = status;
        }
    }
    public class ServerInfoMessageEventArgs : EventArgs
    {
        public ServerInfoMessage Message { get; set; }
        public ServerInfoMessageEventArgs(ServerInfoMessage msg)
        {
            this.Message = msg;
        }
    }
    public class UserSettingsEventArgs : EventArgs
    {
        public PcUserSettings Settings { get; set; }
        public UserSettingsEventArgs(PcUserSettings settings)
        {
            this.Settings = settings;
        }
    }
    public class ClientListEventArgs : EventArgs
    {
        public ClientList ClientList { get; set; }
        public ClientListEventArgs(ClientList clients)
        {
            this.ClientList = clients;
        }
    }

    public class ContactEmoticonEventArgs : EventArgs
    {
        public ContactEmoticonTransfer Emoticon { get; set; }
        public ContactEmoticonEventArgs(ContactEmoticonTransfer emoticon)
        {
            this.Emoticon = emoticon;
        }
    }
    public class AvatarEventArgs : EventArgs
    {
        public AvatarTransfer Avatar { get; set; }
        public AvatarEventArgs(AvatarTransfer avatar)
        {
            this.Avatar = avatar;
        }
    }
    public class ChatReceiptEventArgs : EventArgs
    {
        public ChatReceipt Receipt { get; set; }
        public ChatReceiptEventArgs(ChatReceipt receipt)
        {
            this.Receipt = receipt; ;
        }
    }

}
